package org.zoomdev.zoom.dao;

import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.expression.Symbol;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.common.filter.pattern.PatternFilterFactory;
import org.zoomdev.zoom.common.utils.Page;
import org.zoomdev.zoom.dao.configs.Query;
import org.zoomdev.zoom.dao.configs.Where;
import org.zoomdev.zoom.dao.entity.Field2ColumnMappingConfig;
import org.zoomdev.zoom.dao.executor.*;
import org.zoomdev.zoom.dao.handler.*;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public final class ActiveRecord<T> extends SqlConfig implements Ar<T> {
    private static final ThreadLocal<ActiveRecord> holder = new ThreadLocal<>();
    private static final BatchUpdateExecutor batchUpdateExecutor = new BatchUpdateExecutor();
    private static final BatchInsertExecutor batchInsertExecutor = new BatchInsertExecutor();
    private static final InsertExecutor insertExecutor = new InsertExecutor();
    private static final UpdateExecutor updateExecutor = new UpdateExecutor();
    private static final DeleteExecutor deleteExecutor = new DeleteExecutor();

    private final static OneResultSetHandler oneHandler = new OneResultSetHandler();
    private final static ListResultSetHandler listHandler = new ListResultSetHandler();

    private final static IntResultSetHandler intResultSetHandler = new IntResultSetHandler();
    private final static ObjectResultSetHandler objectResultSetHandler = new ObjectResultSetHandler();
    private final static ValuesResultSetHandler valuesResultSetHandler = new ValuesResultSetHandler();
    private static final ConcurrentMap<String, Filter<Field2ColumnMappingConfig>> patterFilterCache = new ConcurrentHashMap<>();

    public ActiveRecord() {

    }

    public static void release() {
        holder.remove();
    }

    @SuppressWarnings("unchecked")
    public static <T> ActiveRecord<T> ar() {
        ActiveRecord<T> ar = holder.get();
        if (ar == null) {
            ar = new ActiveRecord<>();
            holder.set(ar);
        }
        return ar;
    }

    public Ar<T> attach(Dao dao) {
        this.setDao(dao);
        return this;
    }

    public Ar<T> table(String table) {
        this.setEntity(getDao().getEntity(table));
        return this;
    }

    @SuppressWarnings("unchecked")
    public T get() {
        try {
            return (T) QueryExecutor.handle(this, oneHandler);
        } finally {
            clear();
        }
    }

    public <R> R value(String column, Class<R> type) {
        try {
            Field2ColumnMappingConfig config = getEntity().getConfig(column);
            getQuery().selectRaw(config.getColumnName());
            Object ret = QueryExecutor.handle(this, objectResultSetHandler);
            return Caster.to(ret, type);
        } finally {
            clear();
        }
    }

    @Override
    public Values values() {
        return (Values) QueryExecutor.handle(this, valuesResultSetHandler);
    }

    @SuppressWarnings("unchecked")
    public Page<T> page(int page, int pageSize) {
        try {
            Query query = getQuery();
            query.setLimit(page, pageSize);
            List<T> list = (List<T>) QueryExecutor.handle(this, listHandler);
            query.clear();
            query.selectFunc("count(*)");
            int count = (Integer) QueryExecutor.handle(this, intResultSetHandler);
            return new Page<>(list, page, pageSize, count);
        } finally {
            clear();
        }
    }

    @SuppressWarnings("unchecked")
    public List<T> limit(int position, int limit) {
        try {
            getQuery().setLimit(position, limit);
            return (List<T>) QueryExecutor.handle(this, listHandler);
        } finally {
            clear();
        }
    }

    @SuppressWarnings("unchecked")
    public List<T> find() {
        try {
            return (List<T>) QueryExecutor.handle(this, listHandler);
        } finally {
            clear();
        }
    }

    public int update(T data) {
        try {
            return updateExecutor.execute(this, data);
        } finally {
            clear();
        }
    }

    public int delete(T data) {
        try {
            return deleteExecutor.execute(this, data);
        } finally {
            clear();
        }
    }

    public int[] update(List<T> list) {
        try {
            return batchUpdateExecutor.executeList(this, list);
        } finally {
            clear();
        }
    }

    public int insert(T data) {
        try {
            return insertExecutor.execute(this, data);
        } finally {
            clear();
        }
    }

    public int[] insert(List<T> list) {
        try {
            return batchInsertExecutor.executeList(this, list);
        } finally {
            clear();
        }
    }

    public int count() {
        try {
            getQuery().selectFunc("count(*)");
            return (int) QueryExecutor.handle(this, intResultSetHandler);
        } finally {
            clear();
        }
    }

    public List<BatchResult> commit() {
        return getBatchResults();
    }

    public Ar<T> batch(boolean value) {
        setBatch(value);
        return this;
    }

    public Ar<T> filter(String columns) {
        setFilter(createPatternFilter(columns));
        return this;
    }

    @Override
    public Ar<T> set(String key, Object value) {
        Object data = getTmpData();
        if (data == null) {
            data = getEntity().newInstance();
            setTmpData(data);
        }

        for (Field2ColumnMappingConfig toField : getEntity().getToFields()) {
            if (toField.match(key)) {
                toField.set(data, Caster.toType(value, toField.getFieldType()));
            }
        }

        return this;
    }

    @Override
    public int update() {
        return update((T) getTmpData());
    }

    @Override
    public int insert() {
        return insert((T) getTmpData());
    }

    @Override
    public Ar<T> where(String key, Object value) {
        getWhere().where(key, value);
        return this;
    }

    @Override
    public Ar<T> orWhere(String key, Symbol symbol, Object value) {
        getWhere().orWhere(key, symbol, value);
        return this;
    }

    @Override
    public Ar<T> orLike(String key, SqlBuilder.Like like, Object value) {
        getWhere().orLike(key, like, value);
        return this;
    }

    @Override
    public Ar<T> like(String key, SqlBuilder.Like like, Object value) {
        getWhere().like(key, like, value);
        return this;
    }

    @Override
    public Ar<T> notLike(String key, SqlBuilder.Like like, Object value) {
        getWhere().notLike(key, like, value);
        return this;
    }

    @Override
    public <E> Ar<T> whereIn(String key, Collection<E> values) {
        getWhere().whereIn(key, values);
        return this;
    }

    @Override
    public Ar<T> where(String key, Symbol symbol, Object value) {
        getWhere().where(key, symbol, value);
        return this;
    }

    @Override
    public Ar<T> whereNotNull(String key) {
        getWhere().whereNotNull(key);
        return this;
    }

    @Override
    public Ar<T> whereNull(String key) {
        getWhere().whereNull(key);
        return this;
    }

    @Override
    public Ar<T> orderBy(String key, SqlBuilder.Sort sort) {
        getQuery().orderBy(key, sort);
        return this;
    }

    @Override
    public Ar<T> groupBy(String key) {
        getQuery().groupBy(key);
        return this;
    }

    @Override
    public Ar<T> having(String key, Symbol symbol, Object value) {
        getQuery().having(getWhere(), key, symbol, value);
        return this;
    }

    @Override
    public Ar<T> join(String table, String on, String type) {
        getQuery().join(table, on, table);
        return this;
    }

    @Override
    public Ar<T> select(String select) {
        getQuery().selectRaw(select);
        return this;
    }

    public Ar<T> selectFunc(String field) {
        getQuery().selectFunc(field);
        return this;
    }

    @Override
    public Ar<T> select(Iterable<String> select) {
        Query query = getQuery();
        for (String str : select) {
            query.selectRaw(str);
        }
        return this;
    }

    @SuppressWarnings("unchecked")
    @Override
    public T get(Object... args) {
        try {
            Field2ColumnMappingConfig[] pks = getEntity().getPrimaryKeys();
            if (args.length != pks.length) {
                throw new DaoException("主键个数应该相同");
            }
            Where where = getWhere();
            for (int i = 0, c = pks.length; i < c; ++i) {
                where.where(pks[i], args[i]);
            }
            return (T) QueryExecutor.handle(this, oneHandler);
        } finally {
            clear();
        }
    }

    public Ar<T> union(String table) {
        return this;
    }

    public Ar<T> unionAll(String table) {
        return this;
    }


    private static Filter<Field2ColumnMappingConfig> createPatternFilter(final String filter) {
        return patterFilterCache.computeIfAbsent(filter,
                (String key) -> new PatterFilter(PatternFilterFactory.createFilter(key)));
    }

    static class PatterFilter implements Filter<Field2ColumnMappingConfig> {

        private final Filter<String> pattern;

        PatterFilter(Filter<String> pattern) {
            this.pattern = pattern;
        }

        @Override
        public boolean accept(Field2ColumnMappingConfig value) {
            return pattern.accept(value.getFieldName());
        }
    }
}
